#include <stdio.h>
#include <vector>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <thread>
#include <string.h>
#include <cerrno>
#include <list>
#include <map>
#include <signal.h>
#include <mutex>
#include <sys/types.h>
#include <sys/wait.h>

#include "http.h"
#include "json.h"

std::mutex mutex;
std::string home;
std::map<std::string, std::string> file;
std::map<std::string, std::string> post;
std::map<std::string, std::string> get;

static void client_thread(int socket_fd) {
    char s[1024];
    enum {
        OP_NULL,
        OP_GET,
        OP_POST
    } op = OP_NULL;
    bool keep_alive = false;;
    char cmd[1024];
    std::string fullname;
    char host[1024];
    int n;
    ssize_t size = 0;
    struct sockaddr_in sin;
    socklen_t ss = sizeof(sin);
    getpeername(socket_fd, (sockaddr *)&sin, &ss);

    cmd[0] = 0;
    host[0] = 0;

    while ((n = sock_recvln(socket_fd, s, sizeof(s))) > 0) {
        if (s[0] == 0) break;
        s[n] = 0;
printf("%s", s);
        if (memcmp(s, "POST", 4) == 0) {
            op = OP_POST;
            sscanf(s, "POST %s HTTP/1.1", cmd);
        } else if (memcmp(s, "GET", 3) == 0) {
            op = OP_GET;
            sscanf(s, "GET %s HTTP/1.1", cmd);
        } else if (memcmp(s, "HOST:", 5) == 0) {
            sscanf(s, "Host: %s", host);
        } else if (memcmp(s, "Content-Length:", 15) == 0) {
            sscanf(s, "Content-Length: %ld", &size);
        } else if (memcmp(s, "Connection:", 11) == 0) {
            int i = 12;
            while (s[i] == ' ') i++;
            if (memcmp(s + i, "keep-alive", 10) == 0) {
                keep_alive = true;
            } else {
                keep_alive = false;
            }
        } else if (s[0] == '\n' || s[0] == '\r') {
            if (op == OP_POST) {
                fullname = home + post[cmd];
                if (post.find(cmd) != post.end()) {
                    pid_t pid = fork();
                    if (pid == 0) {
                        setenv("cmd", cmd, 1);
                        setenv("HTTP_OP", "POST", 1);
                        sprintf(cmd, "%d", socket_fd);
                        setenv("socket_fd", cmd, 1);
                        sprintf(cmd, "%ld", size);
                        setenv("Content_Length", cmd, 1);
                        if (keep_alive) {
                            setenv("keep_alive", "1", 1);
                        } else {
                            setenv("keep_alive", "0", 1);
                        }

                        execl(fullname.c_str(), fullname.c_str(), nullptr);
                        send_404(socket_fd);
                        return;
                    } else if (pid > 0) {
                        int wstatus;
                        waitpid(pid, &wstatus, 0);
                    } else {
                        break;
                    }
                } else {
                    send_404(socket_fd);
                    break;
                }
            } else if (op == OP_GET) {
                if (file.find(cmd) != file.end()) {
                    fullname = home + file[cmd];
                } else {
                    for (size_t i = 1;i < strlen(cmd);i++) {
                        if (memcmp(cmd + i - 1, "/-/", 3) == 0) {
                            strcpy(cmd + i, cmd + i + 2);
                        }
                        if (memcmp(cmd + i - 1, "/../", 4) == 0) {
                            strcpy(cmd + i, cmd + i + 3);
                        }
                    }
                    fullname = home + cmd;
                }
                FILE *f = fopen(fullname.c_str(), "r");
                if (f) {
                    fseek(f, 0, SEEK_END);
                    size_t size = ftell(f);
                    fseek(f, 0, SEEK_SET);
                    send_200(socket_fd, size, "text/html", keep_alive);
                    while (size > 0) {
                        n = fread(cmd, 1, sizeof(cmd) - 1, f);
                        cmd[n] = 0;
                        if (n <= 0) break;
                        size -= n;
                        char *p = cmd;
                        while (n > 0) {
                            int nn = ::send(socket_fd, p, n, 0);
                            if (nn <= 0) {
                                size = 0;
                                break;
                            }
                            p += nn;
                            n -= nn;
                        }
                    }
                    fclose(f);
                } else if (get.find(cmd) != get.end()) {
                    fullname = home + get[cmd];
                    pid_t pid = fork();
                    if (pid == 0) {
                        setenv("cmd", cmd, 1);
                        setenv("HTTP_OP", "GET", 1);
                        sprintf(cmd, "%d", socket_fd);
                        setenv("socket_fd", cmd, 1);
                        sprintf(cmd, "%ld", size);
                        setenv("Content_Length", cmd, 1);
                        if (keep_alive) {
                            setenv("keep_alive", "1", 1);
                        } else {
                            setenv("keep_alive", "0", 1);
                        }

                        execl(fullname.c_str(), fullname.c_str(), nullptr);
                        send_404(socket_fd);
                        return;
                    } else if (pid > 0) {
                        int wstatus;
                        waitpid(pid, &wstatus, 0);
                    } else {
                        break;
                    }
                } else {
                    send_404(socket_fd);
                    break;
                }
            }
            if (keep_alive == false) {
                break;
            }
        }
    }

    close(socket_fd);
}

void init_daemon(void) {
    int pid;
    int i;
    if ((pid = fork()) > 0) exit(0);
    if (pid < 0) exit(1);
    for (i = 0;i < NOFILE;++i) {
        //close(i);
    }

    chdir("~");
    umask(0);
    return;
}

int main(int argc, char *argv[]) {
    init_daemon();
    uint16_t port_num = 80;
    uint32_t listen_ip = INADDR_ANY;

    if (argc == 1) {
    } else if (argc == 2) {
        json_t json;
        json.load(argv[1]);
        print_json(json);
        port_num = json["port"].num();
        listen_ip = inet_addr(json["local_addr"].string().c_str());

        home = json["home"].string();
        for (size_t i = 0;i < json["file"].size(); i++) {
            file[json["file"].key(i)] = json["file"][i].string();
        }

        for (size_t i = 0;i < json["post"].size(); i++) {
            post[json["post"].key(i)] = json["post"][i].string();
        }

        for (size_t i = 0;i < json["get"].size(); i++) {
            get[json["get"].key(i)] = json["get"][i].string();
        }
    } else {
        printf("Usage: httpdd [conf.json]\n");
        exit(0);
    }
    signal(SIGPIPE, SIG_IGN);

    int socket_fd, client_fd;
    struct sockaddr_in sin;
    socklen_t size = sizeof(sin);
    
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = listen_ip;
    sin.sin_port = htons(port_num);
    for (;;) {
        if (bind(socket_fd, (sockaddr *)&sin, size) != 0) {
            printf("Fail to bind.\n");
            sleep(1);
            continue;
        }
        break;
    }

    if (listen(socket_fd, 16384) != 0) {
        printf("Fail to listen.\n");
        return errno;
    }

    while ((client_fd = accept(socket_fd, (struct sockaddr *)&sin, &size)) > 0) {
        std::thread(client_thread, client_fd).detach();
    }
    close(socket_fd);

    return 0;
}